/**
 * \file main.cc
 * \brief i8040 driver kernel module.
 */

/*
   Copyright 2007 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <kernel.h>
#include <modinit.h>
#include <TQueue.h>
#include <Namespace.h>
#include <Process.h>
#include <syscall.h>
#include <platform/Interrupt.h>

#include "input.h"
#include "i8042.h"
#include "libps2.h"
#include "psmouse.h"
#include "ps2.h"

#include <stdio.h>
#include <i386/console.h>

const char *module_name = "i8042";

static void keyboardHandler(int irq, unsigned char scode, unsigned long dfl);
static void mouseHandler(int irq, unsigned char scode, unsigned long dfl);

void input_report_key(unsigned int code, int value);
void input_report_rel(unsigned int code, int value);
void input_sync(void);

static psmouse localmouse;

/*
 * Process that has ownership of, and thus access to, the input event queue
 */
static RefProcess owner(0);

/*
 * Input event queue
 */
#define MAX_EVENTS 11
static RWCircularQueue<input_event_t> inputEvents(MAX_EVENTS);

// mouse state
static volatile int mouseDx, mouseDy;
static volatile bool mouseButton1, mouseButton2, mouseButton3;

static bool syscallHandler(regs_t *regs);

namespace hw {
	namespace i8042 {
		static Var keyboard("hw.i8042.keyboard", 0LL, VF_SYSTEM);
		static Var mouse("hw.i8042.mouse", 0LL, VF_SYSTEM);
	}
}

/*
 * Init thread to insulate the rest of the system init from delays in starting
 * up the relatively slow i8042 hardware.
 */
static void init(void *) {
	if (i8042_init() != 0) {
		printf("Error initializing input ports\n");
		return;
	}
	
	if (i8042_bind(I8042_BIND_KBD, keyboardHandler) != 0) {
		printf("Error binding keyboard port\n");
		return;
	}
	
	if (i8042_bind(I8042_BIND_AUX, mouseHandler) != 0) {
		printf("Error binding mouse port\n");
		return;
	}
	
	// give the interrupt handler tasks a chance to start up
	syscall(SYSCALL_THREAD, SYSCALL_THREAD_SLEEP, 500);
	
	psmouse_init();
	psmouse_connect(&localmouse);
	
	// register status vars
	hw::i8042::keyboard = 1;
	hw::i8042::mouse = 1;
}

extern "C"
void __init module_init(void) {
	Namespace::registerVar(hw::i8042::keyboard);
	Namespace::registerVar(hw::i8042::mouse);
	
	// registering the syscall handler now is ok, because it's really just an
	// interface to an event queue. the queue will stay empty until the hardware
	// init finishes, which is perfectly fine
	Interrupt::registerHandler(INT_SYSCALL, syscallHandler);
		
	RefThread thread = Thread::createThread(init, 0, "i8042 init",
		Process::getSystemProcess());
	thread->run();
}

extern "C"
void __finalize module_finalize(void) {
	Namespace::unregisterVar(hw::i8042::keyboard);
	Namespace::unregisterVar(hw::i8042::mouse);
}

#define PARAM(n) (syscallParam(regs, (n)))

static bool syscallHandler(regs_t *regs) {
	bool handled = false;
	
	switch (PARAM(0)) {
		case SYSCALL_I8042: {
			switch (PARAM(1)) {
				case SYSCALL_I8042_QUERY:
					regs->eax = PARAM(2) == 0xdeadbeef ? 0xfeedbeef : 0;
					break;
				
				case SYSCALL_I8042_REQUEST_OWNERSHIP:
					// TODO: add valid flag to kernel::Process so that dead
					// processes will automatically release ownership
					if (!owner) {
						owner = Process::getCurrentProcess();
						regs->eax = true;
					} else {
						// input interface is already owned
						regs->eax = false;
					}
					break;
					
				case SYSCALL_I8042_RELEASE_OWNERSHIP: {
					RefProcess proc = Process::getCurrentProcess();
					if (owner == proc) {
						owner = RefProcess(0);
						regs->eax = true;
					} else {
						regs->eax = false;
					}
				}
				break;
				
				case SYSCALL_I8042_PEEK_EVENT: {
					RefProcess proc = Process::getCurrentProcess();
					if (owner == proc) {
						input_event_t *event = (input_event_t *) PARAM(2);
						
						regs->eax = inputEvents.peek(*event);
					} else {
						regs->eax = false;
					}
				}
				break;
							
				case SYSCALL_I8042_GET_EVENT: {
					RefProcess proc = Process::getCurrentProcess();
					if (owner == proc) {
						input_event_t *event = (input_event_t *) PARAM(2);
						
						inputEvents.dequeue(*event);
						regs->eax = true;
					} else {
						regs->eax = false;
					}
				}
				break;	
				
				default:
					printf("Unhandled SYSCALL_I8042, subfunction %x\n",
						PARAM(1));
					break;
			}
			
			handled = true;
		}
		break;
	}
	
	return handled;
}

static const int KeyCodeSingle[] = {
// 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
0xff,  53,  18,  19,  20,  21,  23,  22,  26,  28,  25,  29,  27,  24,0xff,  48, // 0
  12,  13,  14,  15,  17,  16,  32,  34,  31,  35,  33,  30,  36,  54,   0,   1, // 1
   2,   3,   5,   4,  38,  40,  37,  41,  39,  50,  56,  42,   6,   7,   8,   9, // 2
  11,  45,  46,  43,  47,  44,  56,  76,  55,  49,  57, 122, 120,  99, 118,  96, // 3
  97,  98, 100, 101, 109,  71, 107,  89,  91,  92,  78,  86,  87,  60,  69, 119, // 4
  61, 121, 114,  65,0xff,0xff,0xff, 103, 111,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 5
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 6
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 7
};

static const int KeyCodeMulti[] = {
// 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 0
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,  76,  54,0xff,0xff, // 1
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 2
0xff,0xff,0xff,0xff,0xff,  75,0xff,0xff,  55,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 3
0xff,0xff,0xff,0xff,0xff,0xff,0xff, 115,  62, 116,0xff,  59,0xff,  60,0xff, 119, // 4
  61, 121, 114, 117,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 5
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 6
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 7
};

static void keyboardHandler(int irq, unsigned char scode, unsigned long dfl) {
	static int lastCode = 0;
	bool multi = lastCode == 0xe0;
	
	// save the key up event bit
	uint8 keyUpBit = scode & 0x80;
	
	// store the last received code
	lastCode = scode;
	
	int keyCode = multi ? KeyCodeMulti[scode] : KeyCodeSingle[scode];
	
	printf_xy(30, 24, BLUE, "%02x%02x %03d", multi ? lastCode : 0, scode,
		keyCode);
	
	if (keyCode != 0xff) {
		input_event_t event;
		event.type = INPUT_EVENT_KEY;
		event.code = keyCode;
		event.value = keyUpBit == 0x80;
		
		if (!inputEvents.isFull()) {
			inputEvents.enqueue(event);
		}
	}
}

static void mouseHandler(int irq, unsigned char data, unsigned long dfl) {
	psmouse_interrupt(&localmouse, data, dfl);
}

void input_report_key(unsigned int code, int value) {
	switch (code) {
		case BTN_LEFT:
			mouseButton1 = value;
			break;
		
		case BTN_RIGHT:
			mouseButton3 = value;
			break;
		
		case BTN_MIDDLE:
			mouseButton2 = value;
			break;
	}
	
	input_event_t event;
	event.type = INPUT_EVENT_KEY;
	event.code = code;
	event.value = value;
	
	if (!inputEvents.isFull()) {
		inputEvents.enqueue(event);
	}
}

void input_report_rel(unsigned int code, int value) {
	switch (code) {
		case REL_X:
			mouseDx = value; //(signed char) value & 0x7f;
			break;
		
		case REL_Y:
			mouseDy = value; //(signed char) value & 0x7f;
			break;
		
		case REL_WHEEL:
			break;
	}
	
	input_event_t event;
	event.type = INPUT_EVENT_REL;
	event.code = code;
	event.value = value;
	
	if (!inputEvents.isFull()) {
		inputEvents.enqueue(event);
	}
}

void input_sync(void) {
	// send the packet
	/*cuda_send_packet(ADB_PACKET, 4, 0x40, 0x3c,
		!mouseButton1 ? mouseDy | 0x80 : mouseDy,
		!mouseButton3 ? mouseDx | 0x80 : mouseDx);*/
	printf_xy(0, 24, GREEN, "%08d, %08d", mouseDx, mouseDy);
	printf_xy(21, 24, RED, "%s %s %s", mouseButton1 ? "1" : " ",
		mouseButton2 ? "2" : " ", mouseButton3 ? "3" : " ");
}
